package com.um.springboot.starter.service.impl;


import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.MatchMode;
import com.aliyun.oss.model.PolicyConditions;

import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.auth.sts.AssumeRoleRequest;
import com.aliyuncs.auth.sts.AssumeRoleResponse;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.http.ProtocolType;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import com.um.springboot.starter.entity.AppSTS;
import com.um.springboot.starter.entity.STS;
import com.um.springboot.starter.exception.ServiceException;
import com.um.springboot.starter.properties.HshProperties;
import com.um.springboot.starter.service.STSService;
import com.um.springboot.starter.utils.FileUtil;
import com.um.springboot.starter.utils.STSUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Date;

/**
 * @author 邓南辉
 * 日期 2020/8/7
 * 版权所有 广州优匠科技有限公司
 */
@Slf4j
@Component
public class STSServiceImpl implements STSService {

    @Autowired
    private HshProperties hshProperties;

    private String policy;

    @PostConstruct
    private void initPolicy() throws IOException {
        String bucket = hshProperties.getSts().getBucket();
        InputStream is = new ClassPathResource("oss/policy.json").getInputStream();
        byte[] bytes = FileUtil.read(is);

        String policyTemplate = new String(bytes);
        policy = String.format(policyTemplate,bucket,bucket);
    }

    @Override
    public STS getStsToken() {
        if(STSUtil.isEffective()){
            return STSUtil.STS;
        }

        STS sts = refreshSTSToken();
        if (sts != null) {
            STSUtil.STS = sts;
            STSUtil.GET_STS_TIME = System.currentTimeMillis();
        }
        return sts;
    }

    private STS refreshSTSToken() {
        try {
            final AssumeRoleRequest request = new AssumeRoleRequest();
            request.setSysProtocol(ProtocolType.HTTPS);
            request.setMethod(MethodType.GET);
            request.setRoleArn(hshProperties.getSts().getArn());
            request.setRoleSessionName("jbang-oss");
            request.setPolicy(policy); // Optional

            DefaultProfile.addEndpoint("", "", "Sts", hshProperties.getSts().getStsEndpoint());
            // 构造default profile（参数留空，无需添加region ID）
            IClientProfile profile = DefaultProfile.getProfile("", hshProperties.getSts().getAccessKeyId(), hshProperties.getSts().getAccessKeySecret());
            // 用profile构造client
            DefaultAcsClient client = new DefaultAcsClient(profile);
            final AssumeRoleResponse response = client.getAcsResponse(request);

            AssumeRoleResponse.Credentials c = response.getCredentials();
            STS sts = new STS();
            sts.setAccessKeyId(c.getAccessKeyId());
            sts.setAccessKeySecret(c.getAccessKeySecret());
            sts.setEndpoint(hshProperties.getSts().getEndpoint());
            sts.setExpiration(c.getExpiration());
            sts.setSecurityToken(c.getSecurityToken());
            sts.setBucket(hshProperties.getSts().getBucket());
            sts.setRegion(hshProperties.getSts().getRegion());

            return sts;
        } catch (ClientException e) {
            log.error("Failed：", e);
        }

        return null;
    }



    /******************************以下是app端获得token***********************************/

    OSS ossClient;

    public OSS getOssClient() {
        if (ossClient == null) {
            ossClient = new OSSClientBuilder().build(hshProperties.getSts().getEndpoint(),hshProperties.getSts().getAccessKeyId(),hshProperties.getSts().getAccessKeySecret());
        }
        return ossClient;
    }

    /**
     * 获取令牌
     *
     * @param dir 上传路径
     * @return 令牌响应体
     */
    @Override
    public AppSTS getAppToken(String dir) {
        //格式化路径
        String rootPathPrefix = "/";
        if (dir.startsWith(rootPathPrefix)) {
            dir = dir.substring(1);
        }
        //获取源字符串
        long expireEndTime = System.currentTimeMillis() + hshProperties.getSts().getPresignedExpire() * 1000L;
        String policy = this.generatePolicy(dir, expireEndTime);
        String encodePolicy = this.encodePolicy(policy);
        //签名
        String signature = this.sign(policy);
        //装配响应体
        AppSTS appSTS = new AppSTS();
        appSTS.setAccessId(hshProperties.getSts().getAccessKeyId());
        appSTS.setDir(dir);
        appSTS.setExpire(expireEndTime / 1000);
        appSTS.setPolicy(encodePolicy);
        appSTS.setSignature(signature);
        String host = hshProperties.getSts().getEndpoint();
        if (!StrUtil.contains(host, "://")) {
            host = StrUtil.format("https://{}", host);
        }
        host = host.replace("://", StrUtil.format("://{}.", hshProperties.getSts().getBucket()));
        appSTS.setHost(host);
        return appSTS;
    }


    /**
     * 生成令牌规则字符串
     *
     * @param dir        文件上传路径
     * @param expireTime 到期时间
     * @return 令牌规则字符串
     */
    private String generatePolicy(String dir, long expireTime) {
        Date expiration = DateUtil.date(expireTime);
        PolicyConditions policyConds = new PolicyConditions();
        policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, hshProperties.getSts().getMaxSize());
        policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);
        return getOssClient().generatePostPolicy(expiration, policyConds);
    }

    /**
     * 令牌规则BASE64编码
     *
     * @param policy 原字符串
     * @return 编码后字符串
     */
    private String encodePolicy(String policy) {
        String encodedPolicy = null;
        try {
            byte[] binaryData = policy.getBytes(StandardCharsets.UTF_8);
            encodedPolicy = BinaryUtil.toBase64String(binaryData);
            return encodedPolicy;
        } catch (Exception e) {
            log.error(String.format("OSS令牌签名失败，原字符[%s]", policy), e);
            throw new ServiceException("签名失败!");
        }
    }

    /**
     * 签名
     *
     * @param encodePolicy 编码后的令牌规则字符串
     * @return 签名后字符串
     */
    private String sign(String encodePolicy) {
        return getOssClient().calculatePostSignature(encodePolicy);
    }
}
